package com.jadows.tdog.optional;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.expression.Expression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import org.springframework.util.ClassUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * @author taojiacheng
 */
@Slf4j
@Configuration
public class OptionalComponentRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware, ApplicationContextAware {

    private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
    private static final StandardEvaluationContext EVALUATION_CONTEXT = new StandardEvaluationContext();
    private static final SpelExpressionParser PARSER = new SpelExpressionParser();
    private static final Set<String> BASIC_BEANS = new HashSet<>();

    private static Consumer<ApplicationContext> CONSUMER;
    private static BeanFactory BEAN_FACTORY;

    /**
     * Register bean definitions based on the given {@link AnnotationMetadata}.
     *
     * @param annotationMetadata annotation metadata of the importing class
     * @param registry           current bean definition registry
     */
    @Override
    public void registerBeanDefinitions(@NonNull AnnotationMetadata annotationMetadata, @NonNull BeanDefinitionRegistry registry) {
        Map<String, AnnotatedBeanDefinition> beanDefinitionMap = new HashMap<>(25);
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.addIncludeFilter(new AnnotationTypeFilter(OptionalComponent.class));
        // 扫描路径
        Set<String> basePackages = getBasePackages(annotationMetadata);
        Set<BeanDefinition> candidateComponents = new HashSet<>();
        basePackages.forEach(basePackage -> candidateComponents.addAll(scanner.findCandidateComponents(basePackage)));
        CONSUMER = context -> {
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata metadata = beanDefinition.getMetadata();
                    Map<String, Object> attributes = metadata.getAnnotationAttributes(OptionalComponent.class.getCanonicalName());
                    if (attributes == null) {
                        continue;
                    }
                    String beanName = (String) attributes.get("value");
                    String condition = (String) attributes.get("condition");
                    if (StringUtils.isNotBlank(condition) && BooleanUtils.isNotTrue(parseCondition(condition))) {
                        continue;
                    }
                    boolean basic = (boolean) attributes.get("basic");
                    AnnotatedBeanDefinition storedBeanDefinition = beanDefinitionMap.get(beanName);
                    if (storedBeanDefinition == null) {
                        if (basic) {
                            BASIC_BEANS.add(beanName);
                        }
                        beanDefinitionMap.put(beanName, beanDefinition);
                        continue;
                    }
                    if (basic) {
                        if (BASIC_BEANS.contains(beanName)) {
                            throw new IllegalStateException("beanName: " + beanName + " already exists");
                        }
                        continue;
                    }
                    if (BASIC_BEANS.contains(beanName)) {
                        beanDefinitionMap.put(beanName, beanDefinition);
                    } else {
                        throw new IllegalStateException("beanName: " + beanName + " already exists");
                    }
                }
            }
            // 注册bean
            beanDefinitionMap.forEach(registry::registerBeanDefinition);
        };
    }


    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(OptionalComponentScan.class.getCanonicalName());
        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.isNotEmpty(pkg)) {
                basePackages.add(pkg);
            }
        }
        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        return basePackages;
    }


    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(@NonNull AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

    @Override
    public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
        BEAN_FACTORY = beanFactory;
        EVALUATION_CONTEXT.setBeanResolver(new BeanFactoryResolver(beanFactory));
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        if (CONSUMER == null) {
            return;
        }
        CONSUMER.accept(applicationContext);
    }


    /**
     * 解析表达式
     *
     * @param condition 表达式
     * @return 解析结果
     */
    private Boolean parseCondition(String condition) {
        if (BEAN_FACTORY instanceof ConfigurableBeanFactory) {
            condition = ((ConfigurableBeanFactory) BEAN_FACTORY).resolveEmbeddedValue(condition);
        }
        Expression expression = PARSER.parseExpression(condition, PARSER_CONTEXT);
        return expression.getValue(EVALUATION_CONTEXT, Boolean.class);
    }


}
